#include "preHeader.h"
#include "StaticObject.h"
#include "Map/MapInstance.h"
#include "Loot/LootMgr.h"
#include "Spell/Spell.h"
#include "Session/GameServerMgr.h"

StaticObject::StaticObject()
: LocatableObject(TYPE_STATICOBJECT)
, m_pProto(NULL)
, m_pSpawn(NULL)
{
}

StaticObject::~StaticObject()
{
}

void StaticObject::SubInitLuaEnv()
{
	m_blackboard.SetActor(m_pMapInstance->L, this);
	this->binder::setfinal();
}

bool StaticObject::InitStaticObject(const StaticObjectSpawn* pSpawn)
{
	m_pSpawn = pSpawn;
	SetPosition({pSpawn->x, pSpawn->y, pSpawn->z});
	SetOrientation(pSpawn->o);

	m_pProto = GetDBEntry<SObjPrototype>(pSpawn->entry);
	if (m_pProto == NULL) {
		return false;
	}

	AoiActor::SetRadius(pSpawn->radius != .0f ? pSpawn->radius : m_pProto->radius);

	return true;
}

void StaticObject::Update(uint64 diffTime)
{
	LocatableObject::Update(diffTime);
}

void StaticObject::OnPushToWorld()
{
	InitLuaEnv();
	if (m_pProto->spawnScriptId != 0) {
		RunScriptFileById(m_pMapInstance->L, m_pProto->spawnScriptId,
			this, std::string_view(m_pProto->spawnScriptArgs));
	}
	if (m_pProto->playScriptId != 0) {
		RunScriptFileById(m_pMapInstance->L, m_pProto->playScriptId,
			this, std::string_view(m_pProto->playScriptArgs));
	}

	if (m_pProto->sobjFlags.isActivity) {
		NetPacket pack(MS_UPDATE_ACTIVITY_OBJECT_POSITION);
		pack << true << m_guid << m_pProto->sobjTypeId;
		pack << GetInstGuid() << GetPosition();
		sGameServerMgr.BroadcastPacket2AllGameServer(pack);
	}
	LocatableObject::OnPushToWorld();
}

void StaticObject::OnDelete()
{
	if (m_pProto->sobjFlags.isActivity) {
		NetPacket pack(MS_UPDATE_ACTIVITY_OBJECT_POSITION);
		pack << false << m_guid << m_pProto->sobjTypeId;
		pack << GetInstGuid();
		sGameServerMgr.BroadcastPacket2AllGameServer(pack);
	}

	if (m_pSpawn->flags.isRespawn) {
		m_pMapInstance->CreateTimerX([pMapInstance = m_pMapInstance,
			pSpawnPrivate = m_pSpawnPrivate, pSpawn = m_pSpawn]() {
			auto pSObj = pMapInstance->SpawnStaticObject(pSpawn);
			if (pSObj != NULL && pSpawnPrivate) {
				pSObj->SetSpawnPrivate(pSpawnPrivate);
			}
		}, System::Randi(m_pProto->minRespawnTime, m_pProto->maxRespawnTime), 1);
	}
	Player* pPlayer = NULL;
	for (auto& pair : m_triggerInfos) {
		if (IsValidTriggerObj(pair.first, pair.second, &pPlayer)) {
			pPlayer->InterruptFgSpell();
		}
	}
	LocatableObject::OnDelete();
}

const std::string& StaticObject::GetName() const
{
	return I18N_TEXT(m_pProto->sobjTypeId, STRING_TEXT_TYPE::SOBJ_NAME);
}

void StaticObject::BuildCreatePacketForPlayer(INetPacket& pck, Player* pPlayer)
{
	LocatableObject::BuildCreatePacketForPlayer(pck, pPlayer);
	pck << m_pProto->sobjTypeId << m_pSpawn->spawnId << m_pSpawn->radius;
}

void StaticObject::SetSpawnPrivate(const std::shared_ptr<StaticObjectSpawn>& pSpawn)
{
	m_pSpawnPrivate = pSpawn;
}

const std::shared_ptr<StaticObjectSpawn>& StaticObject::GetSpawnPrivate() const
{
	return m_pSpawnPrivate;
}

bool StaticObject::IsValidTriggerObj(
	ObjGUID playerGuid, uint64 spellInstGuid, Player** pPlayerPtr) const
{
	auto pPlayer = m_pMapInstance->GetPlayer(playerGuid);
	if (pPlayer == NULL) {
		return false;
	}
	if (!pPlayer->IsSpellCasting()) {
		return false;
	}
	if (pPlayer->GetSpellCasting()->GetSpellInstGuid() != spellInstGuid) {
		return false;
	}
	if (pPlayerPtr != NULL) {
		*pPlayerPtr = pPlayer;
	}
	return true;
}

GErrorCode StaticObject::CanTrigger(Player* pPlayer)
{
	if (!IsVisibleByPlayer(pPlayer)) {
		return ErrSpellCantInteractive;
	}

	if (m_pProto->sobjReqQuestDoing != 0) {
		if (m_pProto->lootSetID != 0) {
			bool rst = sLootMgr.IsPlayerRequireLoot4Quest(
				pPlayer, m_pProto->lootSetID, m_pProto->sobjReqQuestDoing);
			if (!rst) {
				return ErrSpellCantInteractive;
			}
		}
	}

	if (m_pProto->sobjFlags.isExclusiveTrigger) {
		for (auto itr = m_triggerInfos.begin(); itr != m_triggerInfos.end();) {
			if (!IsValidTriggerObj(itr->first, itr->second)) {
				itr = m_triggerInfos.erase(itr);
			} else {
				return ErrSpellExclusiveMine;
			}
		}
	}

	auto pItemStorage = pPlayer->GetItemStorage();
	for (auto& itemInfo : m_pProto->triggerCostItems) {
		if (!pItemStorage->IsItemEnough(itemInfo.itemId, itemInfo.itemNum)) {
			return ErrSpellLackMineMaterial;
		}
	}

	if (m_pProto->lootSetID != 0) {
		GErrorCode errCode =
			pPlayer->CanLootPrizes2ItemStorage(m_pProto->lootSetID, false);
		if (errCode != CommonSuccess) {
			return errCode;
		}
	}

	return CommonSuccess;
}

void StaticObject::StartTrigger(Player* pPlayer, Spell* pSpell, size_t effectIndex)
{
	m_triggerInfos[pPlayer->GetGuid()] = pSpell->GetSpellInstGuid();
}

void StaticObject::StopTrigger(Player* pPlayer, Spell* pSpell, size_t effectIndex)
{
	m_triggerInfos.erase(pPlayer->GetGuid());
}

void StaticObject::FinishTrigger(Player* pPlayer, Spell* pSpell, size_t effectIndex)
{
	if (m_pProto->lootSetID != 0) {
		pPlayer->LootPrizes2ItemStorage(m_pProto->lootSetID,
			IFT_MINE, {GetEntry()}, CFT_MINE, {GetEntry()});
	}
	if (m_pProto->sobjFlags.isRemoveAfterTriggerDone) {
		FastDisappear();
	}
}

bool StaticObject::IsVisibleByPlayer(const Player* pPlayer)
{
	if (m_pProto->sobjReqQuestDoing != 0) {
		auto pQuestLog = pPlayer->
			GetQuestStorage()->GetQuestLogByEntry(m_pProto->sobjReqQuestDoing);
		if (pQuestLog == NULL) {
			return false;
		}
	}
	return true;
}

GErrorCode StaticObject::CanInteractive(Player* pPlayer)
{
	if (!IsVisibleByPlayer(pPlayer)) {
		return TargetInvalid;
	}
	return CommonSuccess;
}

GErrorCode StaticObject::TryInteractive(Player* pPlayer)
{
	GErrorCode errCode = CanInteractive(pPlayer);
	if (errCode != CommonSuccess) {
		return errCode;
	}

	if (m_pProto->triggerSpellID == 0 || m_pProto->triggerSpellLv == 0) {
		return InvalidRequest;
	}

	return pPlayer->CastWithoutLearnSpell2Target(
		this, m_pProto->triggerSpellID, m_pProto->triggerSpellLv);
}

void StaticObject::ObjectHookEvent_OnSObjUnitEnter(Unit* pUnit)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnSObjUnitEnter, "OnSObjUnitEnter", pUnit);
}

void StaticObject::ObjectHookEvent_OnSObjUnitLeave(Unit* pUnit)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnSObjUnitLeave, "OnSObjUnitLeave", pUnit);
}
